package org.unidata.mdm.rest.v1.meta.service.model;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Objects;

import javax.ws.rs.Consumes;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.cxf.jaxrs.ext.multipart.Attachment;
import org.apache.cxf.jaxrs.ext.multipart.ContentDisposition;
import org.apache.cxf.jaxrs.ext.multipart.Multipart;
import org.springframework.beans.factory.annotation.Autowired;
import org.unidata.mdm.core.service.AsyncExecutor;
import org.unidata.mdm.core.service.SecurityService;
import org.unidata.mdm.core.util.SecurityUtils;
import org.unidata.mdm.meta.constant.IEConstants;
import org.unidata.mdm.meta.context.ExportContext;
import org.unidata.mdm.meta.context.ExportModelRequestContext;
import org.unidata.mdm.meta.context.UploadModelRequestContext;
import org.unidata.mdm.meta.type.input.meta.MetaGraph;
import org.unidata.mdm.rest.v1.meta.converter.graph.MetaGraphDTOToROConverter;
import org.unidata.mdm.rest.v1.meta.exception.MetaRestExceptionIds;
import org.unidata.mdm.rest.v1.meta.ro.model.ExportModelRequestRO;
import org.unidata.mdm.rest.v1.meta.ro.model.ExportModelResultRO;
import org.unidata.mdm.rest.v1.meta.ro.model.UploadModelResultRO;
import org.unidata.mdm.rest.v1.meta.service.AbstractMetaModelRestService;
import org.unidata.mdm.rest.v1.meta.type.rendering.MetaModelInputRenderingAction;
import org.unidata.mdm.system.exception.PlatformBusinessException;
import org.unidata.mdm.system.service.ExecutionService;
import org.unidata.mdm.system.service.RenderingService;
import org.unidata.mdm.system.type.rendering.VoidInputSource;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.parameters.RequestBody;

/**
 * Output rest controller
 *
 * @author Alexandr Serov
 * @since 27.11.2020
 **/
@Path(MetaInputOutputRestService.SERVICE_PATH)
@Consumes({"application/json"})
@Produces({"application/json"})
public class MetaInputOutputRestService extends AbstractMetaModelRestService {

    /**
     * Service path.
     */
    public static final String SERVICE_PATH = "model-ie";

    public static final String SERVICE_TAG = "model-ie";

    private static final String DEFAULT_STORAGE_ID = "default";

    @Autowired
    private ExecutionService executionService;

    @Autowired
    private RenderingService renderingService;

    /**
     * Async executor.
     */
    @Autowired
    private AsyncExecutor asyncExecutor;
    /**
     *
     */
    @Autowired
    private SecurityService securityService;

    /**
     * Upload.
     *
     * @param file the model file
     * @param isOverride is override or not
     * @return the response
     * @throws IOException the exception
     */
    @POST
    @Path("/upload")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    @Operation(
        description = "Import meta model. Only zip is supported.",
        method = HttpMethod.POST,
        requestBody = @RequestBody(content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA), description = "Model file."),
        tags = SERVICE_TAG)
    public UploadModelResultRO uploadModelFile(
        @Multipart(value = "file") Attachment file,
        @Multipart(value = "override") Boolean isOverride) throws IOException {

        UploadModelResultRO result = new UploadModelResultRO();

        MetaGraph graph = executionService.execute(UploadModelRequestContext.builder()
            .setZipPath(saveToTempFile(file))
            .setOverride(BooleanUtils.isTrue(isOverride))
            .build());

        result.setModelGraph(MetaGraphDTOToROConverter.convert(graph));
        return result;
    }

    /**
     * Export meta model to zip file.
     *
     * @param req the export request
     * @return the response
     */
    @POST
    @Path("/export")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    @Operation(description = "Import meta model. Only zip is supported.", method = HttpMethod.POST, tags = SERVICE_TAG)
    public ExportModelResultRO export(final ExportModelRequestRO req) {
        final String token = SecurityUtils.getCurrentUserToken();
        final String storageId = ObjectUtils.defaultIfNull(req.getStorageId(), DEFAULT_STORAGE_ID);
        ExportModelResultRO result = new ExportModelResultRO();
        result.setStorageId(storageId);
        asyncExecutor.async(() -> {
            securityService.authenticate(token, true);
            String location = IEConstants.EXPORT_PATH + storageId + DateFormatUtils.format(System.currentTimeMillis(), "yyyy-MM-dd_HH-mm-ss");

            ExportModelRequestContext.Builder builder
                = new ExportModelRequestContext.Builder()
                .storageId(storageId)
                .path(Paths.get(location))
                .exportContext(new ExportContext(BooleanUtils.isTrue(req.isRoles()), BooleanUtils.isTrue(req.isUsers())));
            renderingService.renderInput(MetaModelInputRenderingAction.EXPORT_META_MODEL_DRAFT, builder, VoidInputSource.INSTANCE);
            executionService.execute(builder.build());
        });
        return result;
    }

    /**
     * Save file to temp folder.
     *
     * @param attachment the attachment
     * @return the java.nio.file. path
     * @throws IOException Signals that an I/O exception has occurred.
     */
    private java.nio.file.Path saveToTempFile(Attachment attachment) throws IOException {
        Objects.requireNonNull(attachment, "Attachment can't be null");
        ContentDisposition contentDisposition = attachment.getContentDisposition();
        String fileName = contentDisposition.getParameter("filename");
        if (StringUtils.isNotBlank(fileName) && StringUtils.endsWith(fileName, ".zip")) {
            String location = String.join(File.separator, System.getProperty("catalina.base"), "temp", "to_import", "model");
            Files.createDirectories(Paths.get(location));
            java.nio.file.Path result = Paths.get(location + File.separator + fileName);
            Files.deleteIfExists(result);
            Files.copy(attachment.getObject(InputStream.class), result);
            return result;
        } else {
            throw new PlatformBusinessException("File format not supported. Supported only zip files.", MetaRestExceptionIds.EX_META_IMPORT_MODEL_INVALID_FILE_FORMAT);
        }
    }

}
